/*******************************************************************************
 * @file    bsp_lowpower.c
 * @brief   Enable Chip enter low power mode.
 *          Chip can wake up in the set time arrived.
 *
 * @version 0.0.1
 *******************************************************************************
 * @license Refer License or other description Docs
 * @author  Felix
 ******************************************************************************/
#include "cpu/bsp_lpower.h"
#include "lptim.h"
#include "lpm.h"
#include "cpu/bsp_watchdog.h"

/****
Local Macros
****/

/****
Local Variables
****/
static volatile uint32_t sSleepSecs = 0;
/* timeout exit flag */
static volatile bool sTMOExit = false;
/* IRQ exit flag */
static volatile bool sIRQExit = false;
static volatile uint32_t sTimerCnt = 0;
static volatile uint32_t sSleepCnt = 0;
static bool use_extl = false;

/****
Local Functions
****/

/* This is interrupt handler */
void LpTim0_IRQHandler(void)
{
    if (TRUE == Lptim_GetItStatus(M0P_LPTIMER0)) {
        Lptim_ClrItStatus(M0P_LPTIMER0);
        ++sTimerCnt;
        uint32_t secs = sSleepSecs;

        if(secs > 0){
            if(sTimerCnt >= secs){
                sTMOExit = true;
            }
        }
    }
}

/****
Global Functions
****/
static bool LptimeInit(bool extl)
{
    stc_lptim_cfg_t lptCfg;
    uint32_t freq = 32767;

    DDL_ZERO_STRUCT(lptCfg);

    use_extl = extl;

    /**
     * default use external low frequency
     */
    if(extl){
        freq = 0xFFFF - 32768;
        lptCfg.enTcksel = LptimXtl;
    } else {
        freq = 0xFFFF - 38400; /* 27135 */
        lptCfg.enTcksel = LptimRcl;
    }

    lptCfg.enGate   = LptimGateLow;
    lptCfg.enGatep  = LptimGatePLow;
    lptCfg.enTogen  = LptimTogEnLow;
    lptCfg.enCt     = LptimTimerFun;    /* Timer feature */
    lptCfg.enMd     = LptimMode2;       /* auto reload */
    lptCfg.u16Arr   = freq;

    Sysctrl_SetPeripheralGate(SysctrlPeripheralLpTim0, TRUE);

    if(Ok != Lptim_Init(M0P_LPTIMER0, &lptCfg)){
        return false;
    }

    /* Enable interrupts */
    Lptim_ClrItStatus(M0P_LPTIMER0);
    Lptim_ConfIt(M0P_LPTIMER0, TRUE);
    EnableNvic(LPTIM_0_1_IRQn, IrqLevel1, TRUE);
}

bool BSP_LPowerInit(bool extl)
{
    sSleepSecs = 0;
    /* timeout exit flag */
    sTMOExit = false;
    /* IRQ exit flag */
    sIRQExit = false;
    sTimerCnt = 0;
    sSleepCnt = 0;
    use_extl = false;
    LptimeInit(extl);
    //RtcInit(extl);

    return true;
}

static uint32_t start = 0;
extern uint32_t gCurTick;

#ifndef RTC_TIME_DIFF
#define RTC_TIME_DIFF       0
#endif

#ifndef SLEEP_ENABLE
#define SLEEP_ENABLE    1
#endif

bool BSP_LPowerSleep(const uint32_t secs)
{
    uint32_t ms = 0;
    uint32_t clkfreq = use_extl ? 32768 : 38400;

    sSleepCnt = 0;
    sTimerCnt = 0;
    sIRQExit = false;
    sTMOExit = false; /* clear flag */
    sSleepSecs = secs;

#if 1
    M0P_LPTIMER0->CNT_f.CNT = 0xffff - clkfreq - RTC_TIME_DIFF;
    uint32_t starttick = gCurTick;
    Lptim_Cmd(M0P_LPTIMER0, TRUE);

    /* Enter low power mode */
    while (false == sTMOExit && false == sIRQExit){
#if SLEEP_ENABLE
        Lpm_GotoDeepSleep(FALSE);
#endif
        ms = M0P_LPTIMER0->CNT_f.CNT;
        sSleepCnt = sTimerCnt;
        BSP_WatchdogFeed();
    }

    Lptim_Cmd(M0P_LPTIMER0, FALSE);

    if (ms > (0xFFFF - clkfreq))
    {
        ms -= (0xFFFF - clkfreq);
        ms = (ms * 1000 + clkfreq / 2 + 1) / clkfreq;
        ms %= 1000;
    }
    else
    {
        ms = 0;
    }

    if (!sTMOExit && ms + sSleepCnt * 1000 < secs * 1000) {
        uint32_t secs1 = sSleepCnt;
        gCurTick = starttick + ms + secs1 * 1000;
    }
    else
    {
        gCurTick = starttick + secs * 1000;
    }
#else
    sTMOExit = 1;
    for (int i = 0; i < secs; i++)
    {
        osDelay(1000);
        BSP_WatchdogFeed();
    }
#endif

    return sTMOExit;
}

bool BSP_LPowerSleepMs(const uint32_t msecs)
{
    uint32_t clkfreq = use_extl ? 32768 : 38400;
    uint32_t ms = 0;
    uint32_t cnt = 0xffff - msecs * clkfreq / 1000;

    if (msecs < 10 || cnt >= 0xfff0)
        return FALSE;

    M0P_LPTIMER0->CNT_f.CNT = cnt;
    sIRQExit = false;
    sTMOExit = false; /* clear flag */
    sSleepSecs = 1;

#if 1
    uint32_t starttick = gCurTick;
    Lptim_Cmd(M0P_LPTIMER0, TRUE);
    /* Enter low power mode */
    while (false == sTMOExit && false == sIRQExit){
#if SLEEP_ENABLE
        /* Enter low power mode */
        Lpm_GotoDeepSleep(FALSE);
#endif
    }

    ms = M0P_LPTIMER0->CNT_f.CNT;
    BSP_WatchdogFeed();

    Lptim_Cmd(M0P_LPTIMER0, FALSE);

    if (sTMOExit)
        ms = msecs;
    else {
        ms -= cnt;
        ms = (ms * 1000 + clkfreq / 2 + 1) / clkfreq;
    }

    if (ms < 1)
        return FALSE;

    if (ms > msecs)
        ms = msecs;

    ms -= 1;

    gCurTick = starttick + ms;
#else
    sTMOExit = 1;
    osDelay(msecs);
#endif

    return sTMOExit;
}

void BSP_LPowerStop(void)
{
    sIRQExit = true;
}

uint32_t BSP_LPowerGetSecs(void)
{
    return sTimerCnt;
}

